package tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import lib.Utility;
import lib.logging.ILogger;
import lib.messaging.Communicator;
import lib.messaging.IReceiver;
import lib.messaging.ISender;
import lib.messaging.NetworkAddress;

import org.junit.Test;

/**
 * Unittests for {@link Communicator}
 *
 * @author abhip
 *
 */
public class CommunicatorTests {
    private class TestClientThread extends Thread {
        private ILogger logger;

        public final String TestMessage = "Hello Server! This is a test message from client!";

        public TestClientThread(ILogger logger) {
            this.logger = logger;
        }

        @Override
        public void run() {
            try {
                SendTestMessage();
            } catch (UnsupportedEncodingException e) {
                logger.LogException(e);
            } catch (UnknownHostException e) {
                logger.LogException(e);
            } catch (IOException e) {
                logger.LogException(e);
            }
        }

        private void SendTestMessage() throws UnknownHostException, IOException {
            ISender client = new Communicator(
                    PacketDropProbability,
                    logger);
            byte[] dataSent = Utility.StringToByteArray(TestMessage);
            client.SendBytes(ServerAddress, dataSent);
        }
    }

    private class TestServerThread extends Thread {
        private String dataReceived = null;
        private ILogger logger;

        public TestServerThread(ILogger logger) {
            this.logger = logger;
        }

        public String DataReceived() {
            return dataReceived;
        }

        private void ReceiveTestMessage() throws IOException,
                ClassNotFoundException {
            IReceiver server = new Communicator(
                    PacketDropProbability,
                    logger);
            byte[] bytesReceived = server.ReceiveBytes(ServerAddress.Port());

            if (bytesReceived == null) {
                dataReceived = null;
            } else {
                dataReceived = Utility.ByteArrayToString(bytesReceived);
            }
        }

        @Override
        public void run() {
            try {
                ReceiveTestMessage();
            } catch (SocketTimeoutException e) {
                logger.LogException(e);
            } catch (UnsupportedEncodingException e) {
                logger.LogException(e);
            } catch (IOException e) {
                logger.LogException(e);
            } catch (ClassNotFoundException e) {
                logger.LogException(e);
            }
        }
    }

    class TestServerWithTimeout {
        private String dataReceived = null;
        private ILogger logger;
        private int timeout;

        public TestServerWithTimeout(ILogger logger, int timeout) {
            this.logger = logger;
            this.timeout = timeout;
        }

        public String DataReceived() {
            return dataReceived;
        }

        public void ReceiveTestMessage() throws IOException,
                ClassNotFoundException {
            IReceiver server = new Communicator(PacketDropProbability, logger);
            byte[] bytesReceived = server.ReceiveBytes(ServerAddress.Port(), timeout);

            if (bytesReceived == null) {
                dataReceived = null;
            } else {
                dataReceived = Utility.ByteArrayToString(bytesReceived);
            }
        }
    }

    private static double PacketDropProbability = 0.0f;
    private static final NetworkAddress ServerAddress = new NetworkAddress("localhost", 2012);

    @Test
    public void ReceiveBytes_GetsBytesSentBySendBytes()
            throws InterruptedException {
        // Arrange:
        ILogger logger = new TestLogger();
        TestServerThread serverThread = new TestServerThread(logger);
        TestClientThread clientThread = new TestClientThread(logger);

        // Act:
        serverThread.start();
        // sleep for some time to allow server thread to open sockets, etc.
        Thread.sleep(200);
        clientThread.start();
        // sleep for some more time to give enough time to server to receive
        // message.
        Thread.sleep(200);

        // Assert:
        assertNotNull("DataReceived should not be null",
                serverThread.DataReceived());
        assertEquals("DataReceived must match dataSent",
                clientThread.TestMessage, serverThread.DataReceived());
    }

    @Test(expected = SocketTimeoutException.class)
    public void ReceiveBytes_OnTimeout_ThrowsSocketTimeoutException()
            throws InterruptedException, IOException, ClassNotFoundException {
        // Arrange:
        int timeout = 200;
        ILogger logger = new TestLogger();
        TestServerWithTimeout serverWithTimeout = new TestServerWithTimeout(
                logger, timeout);

        // Act/Assert:
        serverWithTimeout.ReceiveTestMessage();
    }
}
